#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using C5;

namespace Grawlix.Services.Resources
{
	/// <summary>
	/// An implementation of a URI resolver for the "game" URI
	/// scheme.
	/// </summary>
	[Serializable]
	public class GameResourceResolver :
		ServiceBase<IUriResolver>, IUriResolver
	{
		/// <summary>
		/// The name of the default URI scheme.
		/// </summary>
		private const string GameScheme = "game";

		/// <summary>
		/// The base URI for relative URIs.
		/// </summary>
		private static readonly Uri _baseUri =
			new Uri(GameScheme + "://");

		/// <summary>
		/// The list of resolvers to use to resolve individual schemes.
		/// </summary>
		private readonly ArrayList<IUriResolver> _resolvers =
			new ArrayList<IUriResolver>();

		/// <summary>
		/// Initializes the <see cref="GameResourceResolver"/> class.
		/// </summary>
		static GameResourceResolver()
		{
			UriParser.Register(new FileStyleUriParser(), GameScheme,
			                   -1);
		}

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="GameResourceResolver"/> class.
		/// </summary>
		/// <param name="resolvers">The resolvers to delegate specific
		/// scheme resolution to.</param>
		/// <exception cref="ArgumentNullException"><c>resolvers</c> is
		/// null.</exception>
		/// <exception cref="ArgumentException">A given resolver is
		/// null.</exception>
		public GameResourceResolver(
			IEnumerable<IUriResolver> resolvers)
		{
			if (resolvers == null)
				throw new ArgumentNullException("resolvers");
			foreach (var resolver in resolvers)
			{
				if (resolver == null)
				{
					throw new ArgumentException(
						"A given resolver is null.", "resolvers");
				}
				_resolvers.Add(resolver);
			}
		}

		/// <summary>
		/// Completes the given uri with the default scheme.
		/// </summary>
		/// <param name="uri">The URI to complete. Can be absolute
		/// (will have no effect).</param>
		/// <returns>A completed URI.</returns>
		/// <exception cref="ArgumentNullException"><c>uri</c> is null.
		/// </exception>
		public Uri CompleteUri(Uri uri)
		{
			if (uri == null)
				throw new ArgumentNullException("uri");
			return new Uri(_baseUri, uri);
		}

		/// <summary>
		/// Determines whether the specified scheme is supported.
		/// </summary>
		/// <param name="scheme">The URI scheme to test.</param>
		/// <returns>
		/// 	<c>true</c> if the specified scheme is supported;
		/// otherwise, <c>false</c>.
		/// </returns>
		public bool IsSupported(string scheme)
		{
			return scheme == GameScheme;
		}

		/// <summary>
		/// Loads an embedded resource or Stream by its URI.
		/// </summary>
		/// <param name="uri">The URI that identifies the embedded
		/// resource.</param>
		/// <returns>The embedded resource or Stream found.</returns>
		/// <exception cref="IOException">A generic I/O error occurred.
		/// </exception>
		/// <exception cref="FileNotFoundException">The URI could not
		/// be resolved or does not point to an embedded resource.
		/// </exception>
		/// <remarks>This is a blocking operation which should only be
		/// called asynchronously.</remarks>
		/// <exception cref="ArgumentException">The URI scheme is not
		/// supported.</exception>
		/// <exception cref="ArgumentNullException"><c>uri</c> is null.</exception>
		public object Load(Uri uri)
		{
			object result = TryLoad(uri);
			if (result != null)
				return result;
			throw new FileNotFoundException(
				"A request was made to resolve " +
				"a universal resource identifier (" + uri + "), " +
				"but no such resource was found. " +
				"The resource is not necessarily a critical component, " +
				"but its data will not be accessible.");
		}

		/// <summary>
		/// Tries to load an embedded resource or Stream by its URI.
		/// </summary>
		/// <param name="uri">The URI that identifies the embedded
		/// resource.</param>
		/// <returns>
		/// The embedded resource or Stream found, or null if none
		/// found.
		/// </returns>
		/// <exception cref="IOException">A generic I/O error occurred.
		/// </exception>
		/// <remarks>This is a blocking operation which should only be
		/// called asynchronously.</remarks>
		/// <exception cref="ArgumentException">The URI scheme is not
		/// supported.</exception>
		/// <exception cref="ArgumentNullException"><c>uri</c> is null.
		/// </exception>
		public object TryLoad(Uri uri)
		{
			if (uri == null)
				throw new ArgumentNullException("uri");
			if (!IsSupported(uri.Scheme))
			{
				throw new ArgumentException(
					"unsupported scheme: " + uri.Scheme, "uri");
			}
			uri = new Uri(uri.AbsolutePath.TrimStart('/'), UriKind.Relative);
			foreach (var resolver in _resolvers)
			{
				Uri relative = resolver.CompleteUri(uri);
				object resource = resolver.TryLoad(relative);
				if (resource != null)
					return resource;
			}
			return null;
		}
	}
}